﻿#define MOREINFO

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


namespace ThreeDoorProgram
{
    /// <summary>
    /// 三门问题.
    /// 这个项目是用于验证三门问题的概率.
    /// 所谓三门问题.是指可以选择三个门中一个.而其中一扇门后有奖励,其他门后没有.抽奖人先任选一扇门.
    /// 之后主持人会打开另一扇没有奖励的门,然后抽奖人有权决定是否更换选择另一扇没有打开的门.
    /// 问坚持不换 获得奖励的概率大,还是更换会获得奖励的概率大?
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            // 统计总回合数,以及两种策略下 获得奖励的回合数.
            // 循环
            // 统计结果

            int turn = 10000, currentTurn = 0;
            ThreeDoor2 threeDoor = new ThreeDoor2();
            bool isBingo;
            bool isChange = true; // 是否在主持人揭露后更换选择
            while (true)
            {
                // 按指定策略执行一局游戏
                isBingo = threeDoor.Run(isChange);

                // 每执行1000局游戏公布一次数据
                currentTurn++;
                if (currentTurn>= turn)
                {
                    // 公布数据
                    Console.WriteLine("总局数:" + threeDoor.总执行局数
                        + "\t中奖局数:" + threeDoor.中奖局数
                        +　"[" +threeDoor.中奖比例 + "]"
#if MOREINFO
                        + "\r\n >>>>>>>>>>>>最近一局详情: 中奖的门:" + threeDoor.中奖的门 
                        +",选择的门:"+ threeDoor.选中的门
                        +",排除的门:" + threeDoor.排除的门
                        + "\r\n >>>>>>>>>>>>选择是随机的证明: 中奖门是1号的比例:" + threeDoor.中奖门是1号的比例
                        + ", 选择门是1号的比例:" + threeDoor.选择门是1号的比例
#endif
                        + "\r\n"
                        );

                    currentTurn = 0;
                }
            }
        }


    }

    /// <summary>
    /// 更直观,局外人也能看懂
    /// </summary>
    class ThreeDoor2
    {
        public string 中奖的门 { get { return _中奖的门; } }
        public string 选中的门 { get { return _选中的门; } }
        public string 排除的门 { get { return _排除的门; } }

        public string 中奖门是1号的比例 { get { return (((double)_中奖的门是1号的局数) / _totalNum).ToString("0.000%"); ; } }
        public string 选择门是1号的比例 { get { return (((double)_选中的门是1号的局数) / _totalNum).ToString("0.000%"); ; } }

        public int 总执行局数 { get { return _totalNum; } }
        public int 中奖局数 { get { return _winNum; } }
        public string 中奖比例 { get { return ((double)_winNum / _totalNum).ToString("0.000000%"); } }

        public ThreeDoor2()
        {
            _totalNum = 0;
            _winNum = 0;
            随机数生成器 = new Random(DateTime.Now.Millisecond);
            _可选择的多个门 = new List<string>();

            _中奖的门是1号的局数 = 0;
            _中奖的门是2号的局数 = 0;
            _中奖的门是3号的局数 = 0;

            _选中的门是1号的局数 = 0;
            _选中的门是2号的局数 = 0;
            _选中的门是3号的局数 = 0;
        }
        public bool Run(bool isChange)
        {
            _totalNum++;
            // 构造三门,以及在随机门后放置奖励.
            PrePareDoors();
            // 任选一扇门
            FistSelect();
            // 排除一个没有奖励的门
            OpenEmptyDoor();
            // 按某一种策略 决定是否更换选择
            if (isChange) ChangeSelect();
            // 揭秘结果
            bool isWin = IsBINGO();
            if (isWin)
            {
                _winNum++;
            }
            return isWin;
        }

        #region private

        int _totalNum = 0;
        int _winNum = 0;

        Random 随机数生成器;
        List<string> _可选择的多个门;

        string _中奖的门;
        string _选中的门;
        string _排除的门;

        int _中奖的门是1号的局数;
        int _中奖的门是2号的局数;
        int _中奖的门是3号的局数;

        int _选中的门是1号的局数;
        int _选中的门是2号的局数;
        int _选中的门是3号的局数;

        private void PrePareDoors()
        {
            // 可以选择三个门
            _可选择的多个门.Clear();
            _可选择的多个门.Add("1号门");
            _可选择的多个门.Add("2号门");
            _可选择的多个门.Add("3号门");

            // 随机选择一个作为可中奖的门
            int 随机序号 = GetRandomNum(0, 2);
            _中奖的门 = _可选择的多个门[随机序号];

            if(随机序号 == 0)
            {
                _中奖的门是1号的局数++;
            }
            else if(随机序号 == 1)
            {
                _中奖的门是2号的局数++;
            }
            else
            {
                _中奖的门是3号的局数++;
            }
        }
        private void FistSelect()
        {
            // 抽奖人随机选择一个
            int 随机序号 = GetRandomNum(0, 2);
            _选中的门 = _可选择的多个门[随机序号];

            if (随机序号 == 0)
            {
                _选中的门是1号的局数++;
            }
            else if (随机序号 == 1)
            {
                _选中的门是2号的局数++;
            }
            else
            {
                _选中的门是3号的局数++;
            }
        }

        private void OpenEmptyDoor()
        {
            // 主持人排除一个没有奖励的门

            // 排除已选择的门
            _可选择的多个门.Remove(_选中的门);

            // 排除一个没有奖励的门
            if (_中奖的门 == _选中的门)
            {
                // 剩余两个门都是没有奖励的
                int 任意一个剩余门序号 = GetRandomNum(0, 1);
                _排除的门 = _可选择的多个门[任意一个剩余门序号];
                _可选择的多个门.RemoveAt(任意一个剩余门序号);
            }
            else
            {
                // 排除没有奖励的那个门
                string 剩余的一个可选择的门 = _可选择的多个门[0];
                string 剩余的另一个可选择的门 = _可选择的多个门[1];
                if (剩余的一个可选择的门 == _中奖的门)
                {
                    _排除的门 = 剩余的另一个可选择的门;
                    _可选择的多个门.Remove(剩余的另一个可选择的门);
                }
                else
                {
                    _排除的门 = 剩余的一个可选择的门;
                    _可选择的多个门.Remove(剩余的一个可选择的门);
                }
            }
        }

        private void ChangeSelect()
        {
            // 更换选择
            _选中的门 = _可选择的多个门[0];// 这这时候可更换的门只剩下一个
        }

        private bool IsBINGO()
        {
            return _选中的门 == _中奖的门;
        }

        private int GetRandomNum(int min, int max)
        {
            int len = 随机数生成器.Next(1, 10);
               RNGCryptoServiceProvider csp = new RNGCryptoServiceProvider();
            byte[] byteCsp = new byte[len];
            csp.GetBytes(byteCsp);
            byte num=0;
            foreach (var item in byteCsp)
            {
                num += item;
            }
            int rnum = (int)((int)num % (max - min + 1));
            rnum += min;
            return rnum;
        }
        #endregion
    }
}
